package logger

import (
	"bytes"
	"fmt"
	"github.com/natefinch/lumberjack"
	"github.com/sirupsen/logrus"
	"github.com/sirupsen/logrus/hooks/writer"
	"gopkg.in/yaml.v3"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

const (
	defaultConfigFilePath = "./logger.yaml"
	Stdout                = "Stdout"
	Console               = "Console"
	TextFormatter         = "TextFormatter"
	JSONFormatter         = "JSONFormatter"
	CorvinFormatterName   = "CorvinFormatter"
)

const (
	WHITE  = 0
	RED    = 1
	GREEN  = 2
	YELLOW = 3
	BLUE   = 4
	PURPLE = 5
	CYAN   = 6
	GRAY   = 7
)

// ConfigFile It can be set by the flag -logger-config
var ConfigFile = defaultConfigFilePath

// formatter logrus.Formatter Type
var formatter = map[string]FormatterFunc{
	TextFormatter: FormatterFunc(func(*LoggerConfig) logrus.Formatter {
		return &logrus.TextFormatter{
			// 开启颜色打印
			ForceColors: true,
			// 输出完整时间戳
			FullTimestamp: true,
			// 定义时间输出格式
			TimestampFormat: "2006-01-02 15:04:05"}
	}),
	JSONFormatter: FormatterFunc(func(*LoggerConfig) logrus.Formatter {
		return &logrus.JSONFormatter{}
	}),
	CorvinFormatterName: FormatterFunc(func(config *LoggerConfig) logrus.Formatter {
		return &CorvinFormatter{
			Config: config,
		}
	}),
}

var instance *Config
var once sync.Once

var appenders = make(map[string]Appender)

type Logger struct {
	*logrus.Logger
	Config *LoggerConfig
}

type Appender struct {
	io.Writer
	Name string
}

type FormatterFunc func(*LoggerConfig) logrus.Formatter

func (c Config) String() string {
	data, err := yaml.Marshal(c)
	if err != nil {
		return ""
	}
	return string(data)
}

type Config struct {
	Appenders []AppenderConfig `yaml:"appenders"`
	Loggers   LoggersConfig    `yaml:"loggers"`
}

type AppenderConfig struct {
	Name       string `yaml:"name"`
	Output     string `yaml:"output"`
	MaxSize    int    `yaml:"maxSize"`
	MaxAge     int    `yaml:"maxAge"`
	MaxBackups int    `yaml:"maxBackups"`
	LocalTime  bool   `yaml:"localtime"`
	Compress   bool   `yaml:"compress"`
}

type LoggersConfig struct {
	Logger LoggerConfig `yaml:"logger"`
}

// LoggerConfig loggers对象（root logger）
type LoggerConfig struct {
	Name     string                 `yaml:"name"`
	Level    string                 `yaml:"level"`
	Caller   bool                   `yaml:"caller"`
	Console  bool                   `yaml:"console"`
	Format   string                 `yaml:"format"`
	Appender []LoggerAppenderConfig `yaml:"appender"`
}

// LoggerAppenderConfig appender对象
type LoggerAppenderConfig struct {
	Name  string `yaml:"name"`
	Level string `yaml:"level"`
}

// newDefaultConfig 将 yaml 文件进行解析对应的格式
func newDefaultConfig() *Config {
	return &Config{
		Appenders: []AppenderConfig{
			{},
		},
		Loggers: LoggersConfig{},
	}
}

// getConfig 方法会加载并解析配置文件 logger.yaml
func getConfig() *Config {
	once.Do(func() {
		// 读取日志文件的配置 - logger.yaml
		data, err := os.ReadFile(ConfigFile)
		if err != nil {
			// 读取失败
			logrus.Errorf("读取文件失败: %s  错误: %v", ConfigFile, err)
			// instance = newDefaultConfig()
			return
		}
		// new(Config) 函数会分配一个 Config 结构体并返回其指针
		instance = new(Config)
		if err := yaml.Unmarshal(data, instance); err != nil {
			logrus.Errorf("日志配置yaml文件加载失败: %v", err)
			instance = newDefaultConfig()
		}
		// 根据yaml文件的appenders添加记录器
		initAppender(instance)
	})
	return instance
}

// initAppender 根据配置文件初始化日志输出器
func initAppender(allConfig *Config) {
	for _, config := range allConfig.Appenders {
		// 将 appender 中的name以及 Appender 对象作为键值对进行存储
		appenders[config.Name] = newAppender(config)
	}
}

// newAppender 新建日志记录器
func newAppender(config AppenderConfig) Appender {
	// defer log.Printf("name: %s, 记录器创建完成: %s, 输出地址: %s", config.Name, config.Name, config.Output)
	if strings.ToLower(config.Name) == strings.ToLower(Stdout) {
		return Appender{
			Writer: os.Stdout,
			Name:   config.Name,
		}
	}
	appender, t := newFileAppender(config)
	if t {
		return appender
	} else {
		log.Printf("日志记录器未定义: %s", config.Name)
		return Appender{
			Writer: os.Stdout,
			Name:   config.Name,
		}
	}
}

func newFileAppender(config AppenderConfig) (Appender, bool) {
	if abs, err := filepath.Abs(config.Output); err == nil {
		logger := &lumberjack.Logger{
			// 获取文件名字
			Filename:   config.Output,
			MaxSize:    config.MaxSize, // 兆字节
			MaxAge:     config.MaxAge,  // 天数
			MaxBackups: config.MaxBackups,
			LocalTime:  config.LocalTime,
			Compress:   config.Compress,
		}
		return Appender{
			Writer: logger,
			Name:   config.Name,
		}, true
	} else {
		log.Printf("创建文件失败: %s, %v", abs, err)
	}
	log.Printf("滚动文件切割，无效的文件地址。 %s", config.Output)
	return Appender{}, true
}

// New 创建新的 Logger ，使用配置文件中的信息创建新的日志记录器
func New(name string) {
	defer printLogInfo(name)
	yamlConfig := getConfig()

	// 没有传入name报错
	if len(yamlConfig.Loggers.Logger.Name) == 0 || yamlConfig.Loggers.Logger.Name != name {
		logrus.Panicf("logger %s 配置的name在yaml文件中不存在或者配置错误！", name)
	}

	// 1. 给logger设置日志输出级别
	if len(yamlConfig.Loggers.Logger.Level) > 0 {
		if level, err := logrus.ParseLevel(yamlConfig.Loggers.Logger.Level); err == nil {
			logrus.SetLevel(level)
		}
	}
	// 2. 调用者信息
	logrus.SetReportCaller(yamlConfig.Loggers.Logger.Caller)

	// 这里设置打印日志界别最详细，不然这里默认的最高级别为 Info，会导致Hook中设置日志级别控制失效。
	logrus.SetLevel(logrus.TraceLevel)

	// 3. 设置日志输出格式
	if len(yamlConfig.Loggers.Logger.Format) > 0 {
		if fun, ok := formatter[yamlConfig.Loggers.Logger.Format]; ok {
			logrus.SetFormatter(fun(&yamlConfig.Loggers.Logger))
		} else {
			logrus.Panicf("日志文件的日志内容格式配置错误！！！")
		}
	}

	// 4. 添加Hooks
	if len(yamlConfig.Loggers.Logger.Appender) > 0 {
		// 遍历 yaml 文件中的loggers.logger.appender，根据 appender 中的信息添加 Hook 对象。
		for _, appender := range yamlConfig.Loggers.Logger.Appender {
			hook, err := newHook(yamlConfig.Loggers.Logger, appender)
			if err == nil {
				logrus.AddHook(hook)
			} else {
				logrus.Panicf("该%s未定义， %v", appender.Name, err)
			}
		}
	} else {
		logrus.Panicf("未定义任何日志记录器appender")
	}

	logrus.SetOutput(emptyWriter{})
	// return &Logger{logger, &yamlConfig.Loggers.Logger}
}

// newHook 创建日志钩子，用于将日志输出到不同的目标
func newHook(logger LoggerConfig, loggerAppender LoggerAppenderConfig) (logrus.Hook, error) {
	// 获取appender中设置的日志级别
	lvl := loggerAppender.Level
	if len(lvl) == 0 {
		lvl = logger.Level
	}
	levels := make([]logrus.Level, 0)
	if level, err := logrus.ParseLevel(lvl); err == nil {
		for _, ll := range logrus.AllLevels {
			if ll <= level {
				// 将小于yaml文件设定的日志级别的数据添加到切片数组 levels 中
				levels = append(levels, ll)
			}
		}
	}
	// logrus.Infof("%v", levels)
	// 根据 loggers.root.appender 或者 loggers.logger.appender 获取对应的日志记录器
	appender, ok := appenders[loggerAppender.Name]
	if ok {
		return &writer.Hook{
			Writer:    appender,
			LogLevels: levels,
		}, nil
	} else {
		return nil, fmt.Errorf("%s logger un-found appender", loggerAppender.Name)
	}
}

type CorvinFormatter struct {
	Config *LoggerConfig
}

// Format 自定义控制台日志输出格式
func (f CorvinFormatter) Format(entry *logrus.Entry) ([]byte, error) {
	var color int
	switch entry.Level {
	case logrus.PanicLevel:
		color = RED
	case logrus.FatalLevel:
		color = RED
	case logrus.ErrorLevel:
		color = RED
	case logrus.WarnLevel:
		color = YELLOW
	case logrus.InfoLevel:
		color = GREEN
	case logrus.DebugLevel:
		color = GRAY
	case logrus.TraceLevel:
		color = GRAY
	default:
		color = 0
	}
	// 设置文本的缓冲区
	var b *bytes.Buffer
	if entry.Buffer == nil {
		b = &bytes.Buffer{}
	} else {
		b = entry.Buffer
	}

	// 文件的行号
	fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)

	// 设置格式
	fmt.Fprintf(b, "\033[3%dm%s\t%s\t%s - %s", color, entry.Time.Format("2006-01-02 15:04:06"), entry.Level, fileVal, entry.Message)

	// 输出附加字段
	for key, value := range entry.Data {
		fmt.Fprintf(b, " %s=%v", key, value)
	}

	fmt.Fprint(b, "\033[0m\n")

	return b.Bytes(), nil
}

type emptyWriter struct {
}

func (w emptyWriter) Write([]byte) (n int, err error) {
	return 0, nil
}

func printLogInfo(name string) {
	logrus.Infof("完成logger的创建: %s", name)
	logrus.Info(instance)
}
